// batchExportOptimized.ts
import PizZip from "pizzip";
import { saveAs } from "file-saver";
import { WorkerPool } from "./workerPool";
import { MemoryGuard } from "./memoryGuard";
import type { RenderTask, RenderResult } from "./docxTypes"; //  从共享类型文件引入

export interface TemplateData {
  title: string;
  name: string;
  content: string;
  signer: string;
  sign_time: string;
  [key: string]: unknown; //  建议加上索引签名，避免后续扩展字段再报错
}

export interface TemplateData {
  title: string;
  name: string;
  content: string;
  signer: string;
  sign_time: string;
}

export type BatchItem = {
  filename: string;
  data: any;
};

const fetchAsArrayBuffer = async (url: string) => {
  const res = await fetch(url, { cache: "no-store" });
  if (!res.ok) throw new Error(`获取模板失败: ${res.status} ${res.statusText}`);
  return await res.arrayBuffer();
};

function pickConcurrency() {
  const cores = typeof navigator !== "undefined" ? (navigator.hardwareConcurrency || 4) : 4;
  const memGB = (navigator as any)?.deviceMemory || 4; // 1/2/4/8…
  // 低内存设备降档；高内存设备适当提升
  const base = Math.min(Math.max(2, Math.floor(cores / 2)), 6);
  if (memGB <= 2) return Math.max(2, Math.min(base, 3));
  if (memGB >= 8) return Math.min(base + 1, 8);
  return base;
}

function calcInFlightLimitBytes() {
  const memGB = (navigator as any)?.deviceMemory || 4;
  // 预留给页面和系统，给导出过程 25%～35% 内存窗口
  const allowanceGB = Math.min( Math.max(0.75, memGB * 0.3), 2.5 ); // 上限 2.5GB
  return Math.floor(allowanceGB * 1024 * 1024 * 1024);
}

async function createZipSink(zipName: string, mode: "auto" | "browser" | "fs" = "auto") {
  const supportsFS = typeof (window as any).showSaveFilePicker === "function";
  const wantFS = mode === "fs" || (mode === "auto" && supportsFS);

  if (wantFS && supportsFS) {
    // —— 流式直写（保持你原来的实现即可）——
    const fileHandle = await (window as any).showSaveFilePicker({
      suggestedName: zipName,
      types: [{ description: "ZIP Archive", accept: { "application/zip": [".zip"] } }],
    });
    const writable = await fileHandle.createWritable();

    const files: { name: string; data: Uint8Array }[] = [];
    const api = {
      async add(name: string, ab: ArrayBuffer) {
        files.push({ name, data: new Uint8Array(ab) });
      },
      async close() {
        const blob = buildSimpleZip(files); // 你的 UTF-8 修复版
        await writable.write(blob);
        await writable.close();
      },
      streamed: true as const,
    };
    return api;
  }

  // —— 浏览器自带下载（PizZip 内存累积 + saveAs）——
  const piz = new PizZip();
  const api = {
    async add(name: string, ab: ArrayBuffer) {
      // PizZip 会正确处理 UTF-8 文件名
      piz.file(name, ab);
    },
    async close() {
      const blob = piz.generate({
        type: "blob",
        compression: "DEFLATE",
        mimeType: "application/zip",
      }) as Blob;
      saveAs(blob, zipName); // ✅ 触发浏览器下载栏
    },
    streamed: false as const,
  };
  return api;
}


// 极简 ZIP（无压缩）：适合流式直写，几乎不占 CPU 与额外内存。
//  UTF-8 安全的极简 ZIP（无压缩，设置了 EFS 标志位）
function buildSimpleZip(files: { name: string; data: Uint8Array }[]): Blob {
  const encoder = new TextEncoder();
  const chunks: Uint8Array[] = [];
  const cd: Uint8Array[] = [];
  let offset = 0;

  const FLAG_UTF8 = 0x0800; // General purpose bit flag: UTF-8
  const VERSION = 20;       // "version needed to extract" = 2.0

  const crc32 = (buf: Uint8Array) => {
    let c = ~0;
    for (let i = 0; i < buf.length; i++) {
      c ^= buf[i];
      for (let k = 0; k < 8; k++) c = (c >>> 1) ^ (0xEDB88320 & -(c & 1));
    }
    return ~c >>> 0;
  };
  const u32 = (n: number) => new Uint8Array([n & 255, (n >> 8) & 255, (n >> 16) & 255, (n >> 24) & 255]);
  const u16 = (n: number) => new Uint8Array([n & 255, (n >> 8) & 255]);

  // 可选：写 DOS 时间/日期，兼容性更好（这里用 0 也行）
  const dosTime = 0, dosDate = 0;

  for (const f of files) {
    //  关键：文件名 UTF-8 & 归一化，避免某些系统的分解字符差异
    const safeName = f.name.normalize ? f.name.normalize("NFC") : f.name;
    const nameBytes = encoder.encode(safeName);

    const crc = crc32(f.data);
    const size = f.data.length;

    // Local File Header
    const LFH = new Uint8Array([
      ...u32(0x04034b50),      // signature
      ...u16(VERSION),         // version needed to extract
      ...u16(FLAG_UTF8),       //  general purpose bit flag (UTF-8)
      ...u16(0),               // compression method (0 = store)
      ...u16(dosTime), ...u16(dosDate), // last mod time/date
      ...u32(crc),
      ...u32(size),
      ...u32(size),
      ...u16(nameBytes.length),
      ...u16(0),               // extra length
    ]);
    chunks.push(LFH, nameBytes, f.data);

    // Central Directory Header
    const CDH = new Uint8Array([
      ...u32(0x02014b50),      // signature
      ...u16(VERSION),         // version made by (随便设成 2.0)
      ...u16(VERSION),         // version needed to extract
      ...u16(FLAG_UTF8),       //  general purpose bit flag (UTF-8)
      ...u16(0),               // compression method
      ...u16(dosTime), ...u16(dosDate),
      ...u32(crc),
      ...u32(size),
      ...u32(size),
      ...u16(nameBytes.length),
      ...u16(0),               // extra length
      ...u16(0),               // file comment length
      ...u16(0),               // disk number start
      ...u16(0),               // internal file attributes
      ...u32(0),               // external file attributes
      ...u32(offset),          // relative offset of local header
    ]);
    cd.push(CDH, nameBytes);

    offset += LFH.length + nameBytes.length + size;
  }

  const cdSize = cd.reduce((n, u) => n + u.length, 0);
  const EoCD = new Uint8Array([
    ...u32(0x06054b50), // end of central directory
    ...u16(0), ...u16(0),           // disk numbers
    ...u16(files.length), ...u16(files.length),
    ...u32(cdSize),
    ...u32(offset),
    ...u16(0),                      // comment length
  ]);

  const all = [...chunks, ...cd, EoCD];
  return new Blob(all, { type: "application/zip" });
}


/**
 * 批量导出（优化版）
 */
export async function exportWordBatchOptimized(
  templateUrl: string,
  items: BatchItem[],
  zipName = "documents.zip",
  options?: {
    maxConcurrency?: number;
    waveSize?: number;          // 每波最多任务数（默认 40）
    estOutputBytes?: number;    // 单份估算输出字节（默认 250KB）
    retries?: number;// 失败重试次数（默认 2）
    // ✅ 新增：表格纵向合并配置（整批共用）
    tableMerge?: {
      enabled: boolean;
      headerTitles?: string[];
      mergeHeaderTexts?: string[];
    };
    // ✅ 新增：指定下载方式
    // 'auto'   = 支持 FS API 就流式，否则回退浏览器下载
    // 'browser'= 强制浏览器下载（不弹目录选择框）
    // 'fs'     = 强制流式写入（仅支持 Chrome/Edge）
    downloadMode?: "auto" | "browser" | "fs";
    onComplete?: (summary: {
      total: number;
      okCount: number;
      failCount: number;
      errors: string[];
      downloadMode: "browser" | "fs";
      streamed: boolean;          // true=流式FS写入；false=浏览器下载
      elapsedMs: number;          // 本次批处理耗时
    }) => void | Promise<void>;
  }
): Promise<void> {
  if (!items.length) return;
  const t0 = performance.now();
  const templateBuffer = await fetchAsArrayBuffer(templateUrl);

  // 并发度
  const concurrency = options?.maxConcurrency ?? pickConcurrency();

  // 内存护栏（在途字节上限）
  const inFlightCap = calcInFlightLimitBytes();
  const guard = new MemoryGuard(inFlightCap);

  // 波次大小
  const wave = Math.max(10, options?.waveSize ?? 40);

  // 估算单份大小（用于预占位）
  const estBytes = Math.max(64 * 1024, options?.estOutputBytes ?? 250 * 1024);

  const pool = new WorkerPool<RenderTask<TemplateData>, RenderResult>(
    concurrency,
    () => new Worker(new URL("./docxWorker.ts", import.meta.url), { type: "module" }),
    (w, t) => w.postMessage(t, [t.templateBuffer])
  );

  // 预热（可选）
  //pool.warmup({ id: "warmup", templateBuffer: templateBuffer.slice(0, 1), data: {} });

  // 选择 ZIP 输出策略
  const sink = await createZipSink(zipName,options?.downloadMode);

  let ok = 0;
  const errors: string[] = [];

  const runOne = async (filename: string, data: TemplateData, idx: number) => {
    // 内存护栏：预占
    await guard.acquire(estBytes);

    const task: RenderTask<TemplateData> = {
      id: `t-${Date.now()}-${idx}`,
      templateBuffer: templateBuffer.slice(0),
      data, //  直接传
      merge: options?.tableMerge
    };


    let attempt = 0;
    const maxRetry = Math.max(0, options?.retries ?? 2);
    // 重试（指数退避）
    while (true) {
      try {
        const res = await pool.run(task);
        if (res.ok) {
          await sink.add(filename, res.arrayBuffer);
          ok++;
          // 以真实大小释放
          guard.release(estBytes);
          break;
        }
        throw new Error(res.error);
      } catch (e: any) {
        attempt++;
        if (attempt > maxRetry) {
          errors.push(`[${filename}] ${e?.message || String(e)}`);
          guard.release(estBytes);
          break;
        }
        // 退避
        await new Promise((r) => setTimeout(r, 300 * attempt));
      }
    }
  };

  // 分波次执行
  for (let i = 0; i < items.length; i += wave) {
    const slice = items.slice(i, i + wave);
    await Promise.all(slice.map((it, k) => runOne(it.filename, it.data, i + k)));
    // 让浏览器喘口气 + 触发渲染
    await new Promise((r) => setTimeout(r, 0));
  }

  await sink.close();
  pool.terminate();
  const elapsedMs = performance.now() - t0;
  // ✅ 组装摘要并回调
  const summary = {
    total: items.length,
    okCount: ok,
    failCount: errors.length,
    errors,
    downloadMode: sink.streamed ? "fs" as const : "browser" as const,
    streamed: sink.streamed,
    elapsedMs
  };

  try {
    await options?.onComplete?.(summary);
  } catch (cbErr) {
    // 回调内部异常不影响主流程
    console.warn("[onComplete] callback error:", cbErr);
  }
  if (errors.length) {
    console.warn("部分失败：\n" + errors.join("\n"));
    alert(`导出完成：成功 ${ok}/${items.length}，失败 ${errors.length}。\n\n失败示例：\n${errors.slice(0, 10).join("\n")}${errors.length > 10 ? "\n..." : ""}`);
  }
}
